1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 import java.util.Arrays;
30 import java.util.Random;
31
32 import javax.sound.midi.MidiChannel;
33 import javax.sound.midi.Receiver;
34 import javax.sound.midi.ShortMessage;
35 import javax.sound.midi.Soundbank;
36 import javax.sound.sampled.AudioFormat;
37 import javax.sound.sampled.AudioInputStream;
38
39 import com.sun.media.sound.AudioFloatConverter;
40 import com.sun.media.sound.AudioSynthesizer;
41 import com.sun.media.sound.ModelAbstractChannelMixer;
42 import com.sun.media.sound.ModelChannelMixer;
43 import com.sun.media.sound.SF2Instrument;
44 import com.sun.media.sound.SF2InstrumentRegion;
45 import com.sun.media.sound.SF2Layer;
46 import com.sun.media.sound.SF2LayerRegion;
47 import com.sun.media.sound.SF2Sample;
48 import com.sun.media.sound.SF2Soundbank;
49 import com.sun.media.sound.SimpleInstrument;
50 import com.sun.media.sound.SimpleSoundbank;
51 import com.sun.media.sound.SoftSynthesizer;
52
53 public class TestPreciseTimestampRendering {
54
55 public static AudioFormat format = new AudioFormat(44100, 16, 1, true,
56 false);
57
58 public static SF2Soundbank createTestSoundbank() {
59
60
61 SF2Soundbank soundbank = new SF2Soundbank();
62 float[] data = new float[100];
63 Arrays.fill(data, 0);
64 data[0] = 1.0f;
65 byte[] bdata = new byte[data.length * format.getFrameSize()];
66 AudioFloatConverter.getConverter(format).toByteArray(data, bdata);
67
68 SF2Sample sample = new SF2Sample(soundbank);
69 sample.setName("Test Sample");
70 sample.setData(bdata);
71 sample.setSampleRate((long) format.getSampleRate());
72 sample.setOriginalPitch(69);
73 soundbank.addResource(sample);
74
75 SF2Layer layer = new SF2Layer(soundbank);
76 layer.setName("Test Layer");
77 soundbank.addResource(layer);
78 SF2LayerRegion region = new SF2LayerRegion();
79 region.setSample(sample);
80 layer.getRegions().add(region);
81
82 SF2Instrument ins = new SF2Instrument(soundbank);
83 ins.setName("Test Instrument");
84 soundbank.addInstrument(ins);
85 SF2InstrumentRegion insregion = new SF2InstrumentRegion();
86 insregion.setLayer(layer);
87 ins.getRegions().add(insregion);
88
89 return soundbank;
90 }
91
92 public static Soundbank createTestSoundbankWithChannelMixer() {
93 SF2Soundbank soundbank = createTestSoundbank();
94
95 SimpleSoundbank simplesoundbank = new SimpleSoundbank();
96 SimpleInstrument simpleinstrument = new SimpleInstrument() {
97
98 public ModelChannelMixer getChannelMixer(MidiChannel channel,
99 AudioFormat format) {
100 return new ModelAbstractChannelMixer() {
101 boolean active = true;
102
103 public boolean process(float[][] buffer, int offset, int len) {
104 for (int i = 0; i < buffer.length; i++) {
105 float[] cbuffer = buffer[i];
106 for (int j = 0; j < cbuffer.length; j++) {
107 cbuffer[j] = -cbuffer[j];
108 }
109 }
110 return active;
111 }
112
113 public void stop() {
114 active = false;
115 }
116 };
117 }
118
119 };
120 simpleinstrument.add(soundbank.getInstruments()[0]);
121 simplesoundbank.addInstrument(simpleinstrument);
122
123 return simplesoundbank;
124 }
125
126 public static void main(String[] args) throws Exception {
127 test(createTestSoundbank());
128 test(createTestSoundbankWithChannelMixer());
129 }
130
131 public static void test(Soundbank soundbank) throws Exception {
132
133
134 AudioSynthesizer synth = new SoftSynthesizer();
135 AudioInputStream stream = synth.openStream(format, null);
136 synth.unloadAllInstruments(synth.getDefaultSoundbank());
137 synth.loadAllInstruments(soundbank);
138 Receiver recv = synth.getReceiver();
139
140
141 ShortMessage reverb_off = new ShortMessage();
142 reverb_off.setMessage(ShortMessage.CONTROL_CHANGE, 91, 0);
143 recv.send(reverb_off, -1);
144 ShortMessage full_volume = new ShortMessage();
145 full_volume.setMessage(ShortMessage.CONTROL_CHANGE, 7, 127);
146 recv.send(full_volume, -1);
147
148 Random random = new Random(3485934583945l);
149
150
151 long[] test_timestamps = new long[30];
152 for (int i = 1; i < test_timestamps.length; i++) {
153 test_timestamps[i] = i * 44100
154 + (int) (random.nextDouble() * 22050.0);
155 }
156
157
158 for (int i = 0; i < test_timestamps.length; i++) {
159 ShortMessage midi_on = new ShortMessage();
160 midi_on.setMessage(ShortMessage.NOTE_ON, 69, 127);
161 recv.send(midi_on,
162 (long) ((test_timestamps[i] / 44100.0) * 1000000.0));
163 }
164
165
166 float[] fbuffer = new float[100];
167 byte[] buffer = new byte[fbuffer.length * format.getFrameSize()];
168 long firsts = -1;
169 int counter = 0;
170 long s = 0;
171 long max_jitter = 0;
172 outerloop: for (int k = 0; k < 10000000; k++) {
173 stream.read(buffer);
174 AudioFloatConverter.getConverter(format).toFloatArray(buffer,
175 fbuffer);
176 for (int i = 0; i < fbuffer.length; i++) {
177 if (fbuffer[i] != 0) {
178 if (firsts == -1)
179 firsts = s;
180
181 long measure_time = (s - firsts);
182 long predicted_time = test_timestamps[counter];
183
184 long jitter = Math.abs(measure_time - predicted_time);
185
186 if (jitter > 10)
187 max_jitter = jitter;
188
189 counter++;
190 if (counter == test_timestamps.length)
191 break outerloop;
192 }
193 s++;
194 }
195 }
196 synth.close();
197
198 if (counter == 0)
199 throw new Exception("Nothing was measured!");
200
201 if (max_jitter != 0) {
202 throw new Exception("Jitter has occurred! "
203 + "(max jitter = " + max_jitter + ")");
204 }
205
206 }
207
208 }